package projet.database;

import java.util.ArrayList;

import projet.CustomException;
import projet.Tools;
import projet.User;
import projet.item.CD;
import projet.item.DVD;
import projet.item.Photo;
import projet.item.Son;
import projet.item.Video;
import projet.metadata.Commentaire;
import projet.metadata.MetaData;
import projet.metadata.Tag;

/**
 * Used to acces and modify a database with the user input
 * 
 * @author CORSINI Pierre, Cyril CHANDELIER, Sylvain BRUN
 */
public class DatabaseAccess {
	/**
	 * Enum of all cmds and the descriptions
	 * 
	 * @author CORSINI Pierre, Cyril CHANDELIER, Sylvain BRUN
	 * @version January 29, 2010
	 */
	enum Cmd {
		addcomment("<id de l'item> : add a comment to the item"),
		addfriend("<username> : add a friend"),
		additem("<photo/video/son/cd/dvd> : add an item to yout list"),
		addtag("<id de l'item> : create a new tag for the item"),
		asstag("<tag1> <tag2> : associate tag2 in a tag1's dictionnary"),
		changepassword("<newPassword> <newPassword> : change your password"),
		edititem("<id> : edit an item"),
		fitems("list friends items"),
		friends("list friends"),
		help("display help informations"),
		items("list your items"),
		logout("end session"),
		playitem("<id de l'item> : lance l'item"),
		removecomment("<itemid> : removes comment from an item"),
		removefriend("<username> : remove a friend"),
		removeitem("<id> : remove an item"),
		removetag("<itemid> <tag> : removes the tag from the item"),
		rename("<newUsername> : rename your username"),
		showitem("<id de l'item> : show item informations"),
		taggeditems("<tag> : display tagged items"),
		update("reload the database from the filesystem"),
		usernames("list usernames");
		String detail;

		private Cmd(String s) {
			this.detail = s;
		}

		@Override
		public String toString() {
			return this.name() + ": " + this.detail;
		}
	}

	private Database database;

	private User loggedUser;

	/**
	 * Constructor
	 * 
	 * @param d The database
	 */
	public DatabaseAccess(Database d) {
		this.database = d;
		this.loggedUser = null;
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_addcomment(String[] args) {
		if (!this.isArgsLengthOk(2, args.length))
			return;
		try {
			int newID = this.database.getFreeMetaId();
			Commentaire comment = new Commentaire(newID, this.loggedUser
					.getId());
			this.database.userAddMeta(this.loggedUser, Integer
					.parseInt(args[1]), comment);

		} catch (CustomException e) {
			e.display();
		}
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_addfriend(String[] args) {
		if (!this.isArgsLengthOk(2, args.length))
			return;

		try {
			this.database.userAddFriend(this.loggedUser, args[1]);
			Tools.print("Friend <" + args[1] + "> added !");
		} catch (CustomException e) {
			e.display();
		}
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_additem(String[] args) {
		if (!this.isArgsLengthOk(2, args.length))
			return;

		try {
			int newID = this.database.getFreeItemId();
			/*
			 * if (args[1].equalsIgnoreCase("item"))
			 * this.database.addItem(this.loggedUser, new Item(newID)); else
			 */
			if (args[1].equalsIgnoreCase("photo"))
				this.database.userAddItem(this.loggedUser, new Photo(newID));
			else if (args[1].equalsIgnoreCase("video"))
				this.database.userAddItem(this.loggedUser, new Video(newID));
			else if (args[1].equalsIgnoreCase("son"))
				this.database.userAddItem(this.loggedUser, new Son(newID));
			else if (args[1].equalsIgnoreCase("CD"))
				this.database.userAddItem(this.loggedUser, new CD(newID));
			else if (args[1].equalsIgnoreCase("DVD"))
				this.database.userAddItem(this.loggedUser, new DVD(newID));
			else
				Tools.printerr("Unknown media !");
		} catch (CustomException e) {
			e.display();
			Tools.printerr("Add item canceled !");
		}
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_addtag(String[] args) {
		if (!this.isArgsLengthOk(2, args.length))
			return;
		try {
			Integer id = Integer.parseInt(args[1]);
			Tag tag = new Tag(this.database.getFreeMetaId());
			this.database.userAddMeta(this.loggedUser, id, tag);
		} catch (CustomException e) {
			e.display();
		} catch (NumberFormatException e) {
			Tools.printerr("Item id must be a number !");
			return;
		}

	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_asstag(String[] args) {
		if (!this.isArgsLengthOk(3, args.length))
			return;

		try {
			this.database.userAssociateTags(args[1], args[2]);
		} catch (CustomException e) {
			e.display();
		}

	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_changepassword(String[] args) {
		if (!this.isArgsLengthOk(3, args.length))
			return;

		if (!args[1].equalsIgnoreCase(args[2])) {
			Tools.printerr("Both password aren't equals !");
			return;
		}

		this.database.userChangePassword(this.loggedUser, args[1]);
		Tools.printerr(2, "Done !");
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_edititem(String[] args) {
		if (!this.isArgsLengthOk(2, args.length))
			return;

		int iid = 0;
		try {
			iid = Integer.parseInt(args[1]);
		} catch (NumberFormatException e) {
			Tools.printerr("Item id must be a number !");
			return;
		}

		try {
			this.database.userEditItem(this.loggedUser, iid);
		} catch (CustomException e) {
			e.display();
			return;
		}
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 */
	private void cmd_fitems() {
		Tools.print(this.database.userGetFriendItemsList(this.loggedUser));
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 */
	private void cmd_friends() {
		Tools.print(this.database.userGetFriendList(this.loggedUser));
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 */
	private void cmd_help() {
		Tools.print("Commands:");
		for (Cmd i : Cmd.values())
			Tools.print(2, i.toString());
		Tools.print("");
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 */
	private void cmd_items() {
		Tools.print(this.database.userGetItemList(this.loggedUser));
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 */
	private void cmd_logout() {
		try {
			this.database.save();
			this.loggedUser = null;
		} catch (CustomException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_playitem(String[] args) {
		if (!this.isArgsLengthOk(2, args.length))
			return;
		try {
			this.database.userPlayItem(this.loggedUser, Integer
					.parseInt(args[1]));
		} catch (NumberFormatException e) {
			Tools.printerr("Not a number");
		} catch (CustomException e) {
			e.display();
		}
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_removecomment(String[] args) {
		if (!this.isArgsLengthOk(2, args.length))
			return;
		Integer itemid;
		ArrayList<MetaData> l;
		try {
			itemid = Integer.parseInt(args[1]);

			l = this.database.userGetMeta(this.loggedUser, itemid);
		} catch (CustomException e1) {
			e1.display();
			return;
		} catch (NumberFormatException e2) {
			Tools.printerr("Item id must be a number !");
			return;
		}

		Commentaire tmp;
		for (MetaData m : l)
			if (m.getClass() == Commentaire.class) {
				tmp = (Commentaire) m;
				if (tmp.getUserId() == this.loggedUser.getId())
					Tools
							.print(2,
									"Would you like to remove this comment ? (yes to accept)");
				Tools.print(2, tmp.getText());
				if (Tools.readLine().equalsIgnoreCase("yes")) {
					try {
						this.database.userRemoveComment(this.loggedUser,
								itemid, tmp.getId());
						Tools.print(2, "Comment deleted !");
					} catch (CustomException e) {
						e.display();
						return;
					}

				}
			}

	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_removefriend(String[] args) {
		if (!this.isArgsLengthOk(2, args.length))
			return;
		try {
			this.database.userRemoveFriend(this.loggedUser, args[1]);
			Tools.print("Friend <" + args[1] + "> removed !");
		} catch (CustomException e) {
			e.display();
		}
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_removeitem(String[] args) {
		if (!this.isArgsLengthOk(2, args.length))
			return;

		try {

			this.database.userRemoveItem(this.loggedUser, Integer
					.parseInt(args[1]));
		} catch (CustomException e) {
			e.display();
			Tools.printerr("Item remove fail !");
			return;
		} catch (NumberFormatException e) {
			Tools.printerr("Item id must be a number !");
			return;
		}

		Tools.print("Item removed !");
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_removetag(String[] args) {
		if (!this.isArgsLengthOk(3, args.length))
			return;

		try {
			this.database.userRemoveTag(this.loggedUser, Integer
					.parseInt(args[1]), args[2]);

		} catch (NumberFormatException e) {
			Tools.printerr("Item id must be a number !");
			return;
		} catch (CustomException e) {
			e.display();
			return;
		}

		Tools.print("Tag removed !");

	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_rename(String[] args) {
		if (!this.isArgsLengthOk(2, args.length))
			return;
		try {
			this.database.userChangeUsername(this.loggedUser, args[1]);
		} catch (CustomException e) {
			e.display();
			Tools.print("Rename cancelled !");
		}

		Tools.print("Your username is now <" + this.loggedUser.getUsername()
				+ "> !");
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_showitem(String[] args) {
		if (!this.isArgsLengthOk(2, args.length))
			return;
		try {
			this.database.userShowItem(this.loggedUser, (Integer
					.parseInt(args[1])));
		} catch (NumberFormatException e) {
			Tools.printerr("Not a number");
		} catch (CustomException e) {
			e.display();
		}
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 * 
	 * @param args parametres associes
	 */
	private void cmd_taggeditems(String[] args) {
		if (!this.isArgsLengthOk(2, args.length))
			return;

		try {
			Tools.print(this.database.userGetItemListWithTag(this.loggedUser,
					args[1]));
		} catch (CustomException e) {
			e.display();
		}

	}

	/**
	 * Execute la commande, cf nom de la fonction
	 */
	private void cmd_update() {
		try {
			this.database.reload();
		} catch (CustomException e) {
			e.display();
			Tools.printerr("Unable to reload!");
		}
	}

	/**
	 * Execute la commande, cf nom de la fonction
	 */
	private void cmd_usernames() {
		System.out.print("Registred usersnames: ");
		for (String u : this.database.userGetUsernames())
			System.out.print(u + " ");
		Tools.print();
	}

	/**
	 * Create new user
	 * 
	 * @param username The user name
	 * @param pw1 The password
	 * @param pw2 The password
	 * @return True if succeed
	 * @throws CustomException
	 */
	public void createUser(String username, String pw1, String pw2)
			throws CustomException {
		if (pw1.contains(" "))
			throw new CustomException("No space allowed in the username !");

		if (!pw1.equals(pw2))
			throw new CustomException("The two passwords aren't the same !");

		this.database.userCreateUser(username, pw1);
	}

	/**
	 * Execute user query
	 * 
	 * @param s The query
	 */
	public void doCmd(String s) {
		String[] args = s.split(" ");
		Cmd currentCmd = null;

		// Find the cmd
		for (Cmd ic : Cmd.values())
			if (args[0].equalsIgnoreCase(ic.name()))
				currentCmd = ic;

		if (currentCmd != null)
			switch (currentCmd) {
			case help:
				this.cmd_help();
				break;

			case logout:
				this.cmd_logout();
				break;

			case rename:
				this.cmd_rename(args);
				break;

			case changepassword:
				this.cmd_changepassword(args);
				break;

			case items:
				this.cmd_items();
				break;

			case asstag:
				this.cmd_asstag(args);
				break;

			case additem:
				this.cmd_additem(args);
				break;

			case showitem:
				this.cmd_showitem(args);
				break;

			case playitem:
				this.cmd_playitem(args);
				break;

			case addtag:
				this.cmd_addtag(args);
				break;

			case taggeditems:
				this.cmd_taggeditems(args);
				break;

			case addcomment:
				this.cmd_addcomment(args);
				break;

			case fitems:
				this.cmd_fitems();
				break;

			case edititem:
				this.cmd_edititem(args);
				break;

			case removetag:
				this.cmd_removetag(args);
				break;

			case removecomment:
				this.cmd_removecomment(args);
				break;

			case removeitem:
				this.cmd_removeitem(args);
				break;

			case usernames:
				this.cmd_usernames();
				break;

			case friends:
				this.cmd_friends();
				break;

			case addfriend:
				this.cmd_addfriend(args);
				break;

			case removefriend:
				this.cmd_removefriend(args);
				break;

			case update:
				this.cmd_update();
				break;
			}
		else
			Tools.print("Unknown command !");

	}

	/**
	 * Tests if arg lenght is ok, display messafe if it's not ok
	 * 
	 * @param wanted The length we want
	 * @param given The length we get
	 * @return
	 */
	private boolean isArgsLengthOk(int wanted, int given) {
		if (wanted == given)
			return true;

		Tools.printerr("Argument number not matching, " + wanted
				+ " arguments needed !");
		return false;
	}

	/**
	 * Try to log a user in the database
	 * 
	 * @param user The user
	 * @param password His pwd
	 * @return True if he succeed
	 * @throws CustomException
	 */
	public void log(String user, String password) throws CustomException {

		this.loggedUser = this.database.userGetUser(user, password);
	}

	/**
	 * Log out current user
	 * 
	 * @throws CustomException
	 */
	public void logout() throws CustomException {
		this.loggedUser = null;
		this.database.save();
	}

	/**
	 * Start the logged user's session
	 */
	public void startSession() {
		Tools.print("You are logged !");
		String query;
		while (this.loggedUser != null) {
			query = Tools.readLine();
			this.doCmd(query);

		}
		Tools.print("Logged OUT!");
	}
}
